<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>D20 Directional Remote</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
text-align: center;
padding: 20px;
background: #3f933f;
color: #d4e4f7;
min-height: 100vh;
margin: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
h1 {
margin-bottom: 30px;
font-size: 2.5em;
color: #c1ff31;
}
.instruction {
font-size: 16px;
color: #ffffff;
margin-bottom: 30px;
}
.dpad-container {
position: relative;
width: 300px;
height: 300px;
margin: 0 auto;
}
.direction-button {
position: absolute;
width: 100px;
height: 100px;
background: #d81159;
border: 3px solid #5c1a33;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
font-weight: bold;
color: #ffffff;
transition: all 0.2s ease;
user-select: none;
}
.direction-button:hover {
background: #6a9bd4;
border-color: #ff6b35;
}
.direction-button:active,
.direction-button.active {
background: #c1ff31;
color: #3f933f;
border-color: #ff6b35;
}
/* Up Button */
.btn-up {
top: 0;
left: 50%;
transform: translateX(-50%);
border-radius: 10px 10px 0 0;
}
.btn-up:active,
.btn-up.active {
transform: translateX(-50%) translateY(-10px);
}
/* Down Button */
.btn-down {
bottom: 0;
left: 50%;
transform: translateX(-50%);
border-radius: 0 0 10px 10px;
}
.btn-down:active,
.btn-down.active {
transform: translateX(-50%) translateY(10px);
}
/* Left Button */
.btn-left {
left: 0;
top: 50%;
transform: translateY(-50%);
border-radius: 10px 0 0 10px;
}
.btn-left:active,
.btn-left.active {
transform: translateY(-50%) translateX(-10px);
}
/* Right Button */
.btn-right {
right: 0;
top: 50%;
transform: translateY(-50%);
border-radius: 0 10px 10px 0;
}
.btn-right:active,
.btn-right.active {
transform: translateY(-50%) translateX(10px);
}
#status {
margin-top: 50px;
font-size: 18px;
color: #c1ff31;
font-weight: bold;
}
</style>
</head>
<body>
<h1>D20 Directional Remote</h1>
<div class="instruction">Click buttons or use arrow keys / WASD</div>
<div class="dpad-container">
<button class="direction-button btn-up" onclick="move('up')" data-direction="up">
↑
</button>
<button class="direction-button btn-down" onclick="move('down')" data-direction="down">
↓
</button>
<button class="direction-button btn-left" onclick="move('left')" data-direction="left">
←
</button>
<button class="direction-button btn-right" onclick="move('right')" data-direction="right">
→
</button>
</div>
<div id="status">Ready</div>
<script>
const status = document.getElementById('status');
let holdInterval = null;
let currentDirection = null;
let isHolding = false;
function move(direction) {
// Update status
if (isHolding) {
status.textContent = `Holding ${direction.toUpperCase()}`;
} else {
status.textContent = `Moving ${direction.toUpperCase()}`;
}
// TODO: Send command to your actuator system here
// Example: fetch(`/move?direction=${direction}`);
}
function startHold(direction) {
if (currentDirection === direction) return; // Already holding this direction
stopHold(); // Stop any previous hold
currentDirection = direction;
isHolding = true;
// Find and activate the button
const button = document.querySelector(`[data-direction="${direction}"]`);
if (button) {
button.classList.add('active');
}
// Initial move
move(direction);
// Continue moving while held (every 100ms)
holdInterval = setInterval(() => {
move(direction);
}, 100);
}
function stopHold() {
if (holdInterval) {
clearInterval(holdInterval);
holdInterval = null;
}
// Remove active class from all buttons
document.querySelectorAll('.direction-button').forEach(b => b.classList.remove('active'));
if (isHolding && currentDirection) {
status.textContent = `Released ${currentDirection.toUpperCase()}`;
}
currentDirection = null;
isHolding = false;
}
// Mouse/Touch controls for buttons
document.querySelectorAll('.direction-button').forEach(button => {
const direction = button.getAttribute('data-direction');
// Mouse down - start holding
button.addEventListener('mousedown', (e) => {
e.preventDefault();
startHold(direction);
});
// Touch start - start holding (for mobile)
button.addEventListener('touchstart', (e) => {
e.preventDefault();
startHold(direction);
});
});
// Global mouse/touch up - stop holding
document.addEventListener('mouseup', stopHold);
document.addEventListener('touchend', stopHold);
// Keyboard controls
let keysPressed = {};
document.addEventListener('keydown', function(event) {
// Prevent default for arrow keys and WASD (scrolling)
if(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'w', 'a', 's', 'd', 'W', 'A', 'S', 'D'].includes(event.key)) {
event.preventDefault();
}
// Prevent repeated keydown events when holding
if (keysPressed[event.key]) return;
keysPressed[event.key] = true;
switch(event.key) {
case 'ArrowUp':
case 'w':
case 'W':
startHold('up');
break;
case 'ArrowDown':
case 's':
case 'S':
startHold('down');
break;
case 'ArrowLeft':
case 'a':
case 'A':
startHold('left');
break;
case 'ArrowRight':
case 'd':
case 'D':
startHold('right');
break;
}
});
document.addEventListener('keyup', function(event) {
keysPressed[event.key] = false;
// Only stop if releasing the currently held direction
switch(event.key) {
case 'ArrowUp':
case 'w':
case 'W':
if (currentDirection === 'up') stopHold();
break;
case 'ArrowDown':
case 's':
case 'S':
if (currentDirection === 'down') stopHold();
break;
case 'ArrowLeft':
case 'a':
case 'A':
if (currentDirection === 'left') stopHold();
break;
case 'ArrowRight':
case 'd':
case 'D':
if (currentDirection === 'right') stopHold();
break;
}
});
</script>
</body>
</html>